﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Reflection;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Reflection.Emit;
using System.Threading;


namespace Slex.Lib.Reactive
{
    public static class ObservableExtensions
    {
        /// <summary>
        /// A custom overload for FromEvent, that can take just the event name, to infer all the
        /// other details
        /// </summary>
        /// <param name="target"></param>
        /// <param name="eventName"></param>
        /// <returns></returns>
        public static IObservable<Event<EventArgs>> FromEvent(object target, string eventName)
        {

            Func<IObserver<Event<EventArgs>>, IDisposable> subscribe = null;

            if (target == null)
            {
                throw new ArgumentNullException("target");
            }
            if (eventName == null)
            {
                throw new ArgumentNullException("eventName");
            }
            EventInfo info = target.GetType().GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance);
            if (info == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Could not find event '{0}' on object of type '{1}'.", new object[] { eventName, target.GetType().FullName }));
            }
            MethodInfo addMethod = info.GetAddMethod();
            MethodInfo removeMethod = info.GetRemoveMethod();
            if (addMethod == null)
            {
                throw new InvalidOperationException("Event is missing the add method.");
            }
            if (removeMethod == null)
            {
                throw new InvalidOperationException("Event is missing the remove method.");
            }
            ParameterInfo[] parameters = addMethod.GetParameters();
            if (parameters.Length != 1)
            {
                throw new InvalidOperationException("Add method should take 1 parameter.");
            }
            MethodInfo method = parameters[0].ParameterType.GetMethod("Invoke");
            ParameterInfo[] infoArray2 = method.GetParameters();
            if (((infoArray2.Length != 2) || !infoArray2[0].ParameterType.Equals(typeof(object))) || (!typeof(EventArgs).IsAssignableFrom(infoArray2[1].ParameterType) || (method.ReturnType != typeof(void))))
            {
                throw new InvalidOperationException("The event delegate must be of the form void Handler(object, T) where T : EventArgs.");
            }
            if (subscribe == null)
            {

                subscribe = delegate(IObserver<Event<EventArgs>> observer)
                {
                    try
                    {

                        EventHandler handler = delegate(object sender, EventArgs eventArgs)
                        {
                            observer.OnNext(new Event<EventArgs>(sender, eventArgs));
                        } ;


                        Delegate d= Delegate.CreateDelegate(info.EventHandlerType, handler.Target, handler.Method, true);
                        addMethod.Invoke(target, new object[] { d });

                        return new AnonymousDisposable(delegate
                        {
                            removeMethod.Invoke(target, new object[] { d });
                        });
                    }
                    catch (Exception ex)
                    {
                        throw new InvalidOperationException(ex.ToString());
                    }
                };
            }

            return new AnonymousObservable<Event<EventArgs>>(subscribe);
        }

    }

     
}
